經過昨天稍微解釋常用到的MPSSE Commands後,今天來做個總結!
來看看OpenOCD中如何實作FTDI-based Adapter的支援!
首先來看一下,FTDI-Based Adapter的Interface(進入點),
參考以下範例(src/jtag/drivers/ftdi.c):
struct jtag_interface ftdi_interface = {
.name = "ftdi",
.supported = DEBUG_CAP_TMS_SEQ,
.commands = ftdi_command_handlers,
.transports = ftdi_transports,
.swd = &ftdi_swd,
.init = ftdi_initialize,
.quit = ftdi_quit,
.speed = ftdi_speed,
.speed_div = ftdi_speed_div,
.khz = ftdi_khz,
.execute_queue = ftdi_execute_queue,
};
底下章節將會說明以下重要的兩個函式:
先看程式碼的部分,參考以下(src/jtag/drivers/ftdi.c):
static int ftdi_initialize(void)
{
if (tap_get_tms_path_len(TAP_IRPAUSE, TAP_IRPAUSE) == 7)
LOG_DEBUG("ftdi interface using 7 step jtag state transitions");
else
LOG_DEBUG("ftdi interface using shortest path jtag state transitions");
///[譯註] Step 1: FTDI USB Open
for (int i = 0; ftdi_vid[i] || ftdi_pid[i]; i++) {
mpsse_ctx = mpsse_open(&ftdi_vid[i], &ftdi_pid[i], ftdi_device_desc,
ftdi_serial, ftdi_location, ftdi_channel);
if (mpsse_ctx)
break;
}
if (!mpsse_ctx)
return ERROR_JTAG_INIT_FAILED;
output = jtag_output_init;
direction = jtag_direction_init;
....中間SWD支援的部分省略!
///[譯註] Step 2: MPSSE Initial & Config
mpsse_set_data_bits_low_byte(mpsse_ctx, output & 0xff, direction & 0xff);
mpsse_set_data_bits_high_byte(mpsse_ctx, output >> 8, direction >> 8);
mpsse_loopback_config(mpsse_ctx, false);
///[譯註] Step 3: Set Adapter Frequency
freq = mpsse_set_frequency(mpsse_ctx, jtag_get_speed_khz() * 1000);
return mpsse_flush(mpsse_ctx);
}
這邊主要可以將整個初始化的流程,分成以下三步驟:
底下分別說明這三部分!
在原流程中,主要是依序帶入OpenOCD Config中設定好的那些USB VID/PID,然後透過呼叫mpsse_open()
,開啟對應的Adapter!
在Config中,我可們可以發現以下內容(interface/olimex-arm-usb-tiny-h.cfg):
ftdi_vid_pid 0x15ba 0x002a <VID2> <PID2> <VID3> <PID3> ....
ftdi_vid_pid後面可以接上成對的VID和PID,以這邊的例子來說,
就是開啟 VID=0x15ba / PID=0x002a的那個Adapter(Olimex ARM-USB-TINY-H)!
接下來研究mpsse_open()的處理流程,參考以下(src/jtag/drivers/mpsse.c):
struct mpsse_ctx *mpsse_open(const uint16_t *vid, const uint16_t *pid, const char *description,
const char *serial, const char *location, int channel)
{
///[譯註] Step 1: 初始化OpeOCD內部資料
struct mpsse_ctx *ctx = calloc(1, sizeof(*ctx));
int err;
if (!ctx)
return 0;
bit_copy_queue_init(&ctx->read_queue);
ctx->read_chunk_size = 16384;
ctx->read_size = 16384;
ctx->write_size = 16384;
ctx->read_chunk = malloc(ctx->read_chunk_size);
ctx->read_buffer = malloc(ctx->read_size);
ctx->write_buffer = malloc(ctx->write_size);
if (!ctx->read_chunk || !ctx->read_buffer || !ctx->write_buffer)
goto error;
ctx->interface = channel;
ctx->index = channel + 1;
ctx->usb_read_timeout = 5000;
ctx->usb_write_timeout = 5000;
err = libusb_init(&ctx->usb_ctx);
if (err != LIBUSB_SUCCESS) {
LOG_ERROR("libusb_init() failed with %s", libusb_error_name(err));
goto error;
}
///[譯註] Step 2: 開啟VID/PID對應的FTDI-based Adapter
if (!open_matching_device(ctx, vid, pid, description, serial, location)) {
/* Four hex digits plus terminating zero each */
char vidstr[5];
char pidstr[5];
LOG_ERROR("unable to open ftdi device with vid %s, pid %s, description '%s', "
"serial '%s' at bus location '%s'",
vid ? sprintf(vidstr, "%04x", *vid), vidstr : "*",
pid ? sprintf(pidstr, "%04x", *pid), pidstr : "*",
description ? description : "*",
serial ? serial : "*",
location ? location : "*");
ctx->usb_dev = 0;
goto error;
}
err = libusb_control_transfer(ctx->usb_dev, FTDI_DEVICE_OUT_REQTYPE,
SIO_SET_LATENCY_TIMER_REQUEST, 255, ctx->index, NULL, 0,
ctx->usb_write_timeout);
if (err < 0) {
LOG_ERROR("unable to set latency timer: %s", libusb_error_name(err));
goto error;
}
///[譯註] Step 3: 將Adapter設定成MPSSE模式
err = libusb_control_transfer(ctx->usb_dev,
FTDI_DEVICE_OUT_REQTYPE,
SIO_SET_BITMODE_REQUEST,
0x0b | (BITMODE_MPSSE << 8),
ctx->index,
NULL,
0,
ctx->usb_write_timeout);
if (err < 0) {
LOG_ERROR("unable to set MPSSE bitmode: %s", libusb_error_name(err));
goto error;
}
///[譯註] Step 4: 清除FTDI晶片內部Tx/Rx Buffer
mpsse_purge(ctx);
return ctx;
error:
mpsse_close(ctx);
return 0;
}
mpsse_open()的處理流程也可以簡化成以下的步驟:
首先是Step 1,初始化內部資料的部分:
struct mpsse_ctx *ctx = calloc(1, sizeof(*ctx));
int err;
if (!ctx)
return 0;
bit_copy_queue_init(&ctx->read_queue);
ctx->read_chunk_size = 16384;
ctx->read_size = 16384;
ctx->write_size = 16384;
ctx->read_chunk = malloc(ctx->read_chunk_size);
ctx->read_buffer = malloc(ctx->read_size);
ctx->write_buffer = malloc(ctx->write_size);
if (!ctx->read_chunk || !ctx->read_buffer || !ctx->write_buffer)
goto error;
ctx->interface = channel;
ctx->index = channel + 1;
ctx->usb_read_timeout = 5000;
ctx->usb_write_timeout = 5000;
err = libusb_init(&ctx->usb_ctx);
if (err != LIBUSB_SUCCESS) {
LOG_ERROR("libusb_init() failed with %s", libusb_error_name(err));
goto error;
}
基本上就是簡單的設定內部Tx/Rx Buffer、資料量上限、Timeout等等資料!
再來是Step 2,開啟對應VID/PID的Adapter,呼叫open_matching_device()
來開啟USB通訊,內部主要是利用libusb相關的函式來做處理,重點是檢查VID/PID和Serial Number是否與Config內設定的相同,有興趣的讀者可以參考附錄的內容!
if (!open_matching_device(ctx, vid, pid, description, serial, location)) {
/* Four hex digits plus terminating zero each */
char vidstr[5];
char pidstr[5];
LOG_ERROR("unable to open ftdi device with vid %s, pid %s, description '%s', "
"serial '%s' at bus location '%s'",
vid ? sprintf(vidstr, "%04x", *vid), vidstr : "*",
pid ? sprintf(pidstr, "%04x", *pid), pidstr : "*",
description ? description : "*",
serial ? serial : "*",
location ? location : "*");
ctx->usb_dev = 0;
goto error;
}
再來是Step 3,將Adapter設定成MPSSE模式
err = libusb_control_transfer(ctx->usb_dev,
FTDI_DEVICE_OUT_REQTYPE,
SIO_SET_BITMODE_REQUEST,
0x0b | (BITMODE_MPSSE << 8),
ctx->index,
NULL,
0,
ctx->usb_write_timeout);
這部分的說明筆者要保留一下,查了很多文件&其他函式庫的實作後,還是不知道為啥這樣設定就可以開啟MPSSE Mode
尤其是筆者還是搞不懂為啥SIO_SET_BITMODE_REQUEST=0x11!?
歡迎留言告知一下!
底下放上參考資料:
如果是使用FTDI Close-source的D2XX Library,可以參考"D2XX Programmer's Guide"中的"FT_SetBitMode"
最後是Step 4,清除FTDI晶片內部Tx/Rx Buffer:
mpsse_purge(ctx);
這部分比較簡單,就是直接呼叫mpsse_purge()來處理!
可以參考以下內容(src/jtag/drivers/mpsse.c):
void mpsse_purge(struct mpsse_ctx *ctx)
{
int err;
LOG_DEBUG("-");
ctx->write_count = 0;
ctx->read_count = 0;
ctx->retval = ERROR_OK;
bit_copy_discard(&ctx->read_queue);
err = libusb_control_transfer(ctx->usb_dev, FTDI_DEVICE_OUT_REQTYPE, SIO_RESET_REQUEST,
SIO_RESET_PURGE_RX, ctx->index, NULL, 0, ctx->usb_write_timeout);
if (err < 0) {
LOG_ERROR("unable to purge ftdi rx buffers: %s", libusb_error_name(err));
return;
}
err = libusb_control_transfer(ctx->usb_dev, FTDI_DEVICE_OUT_REQTYPE, SIO_RESET_REQUEST,
SIO_RESET_PURGE_TX, ctx->index, NULL, 0, ctx->usb_write_timeout);
if (err < 0) {
LOG_ERROR("unable to purge ftdi tx buffers: %s", libusb_error_name(err));
return;
}
}
再來介紹ftdi_initialize()中的Step 2:
///[譯註] Step 2: MPSSE Initial & Config
mpsse_set_data_bits_low_byte(mpsse_ctx, output & 0xff, direction & 0xff);
mpsse_set_data_bits_high_byte(mpsse_ctx, output >> 8, direction >> 8);
mpsse_loopback_config(mpsse_ctx, false);
當FT2232H進入MPSSE Mode後,就要來設定每根訊號線的初始值(Value)和方向(Direction),還記的上篇「Day 24: 您不可不知的FT2232H (2/3) - MPSSE Command Processor」中介紹到的"1.1 Set Data bits(High Byte/Low Byte)"嗎!?
先看一下Config內的設定(interface/olimex-arm-usb-tiny-h.cfg):
ftdi_layout_init 0x0808 0x0a1b
這邊就是將初始值設為0x0808,Direction設為0x0a1b,
可以對照成下表:
Pin # | Function | Value | Direction |
---|---|---|---|
ADBUS0 | TCK | 0 | 1 |
ADBUS1 | TDI | 0 | 1 |
ADBUS2 | TDO | 0 | 0 |
ADBUS3 | TMS | 1 | 1 |
ADBUS4 | ? | 0 | 1 |
ADBUS5 | 0 | 0 | |
ADBUS6 | 0 | 0 | |
ADBUS7 | 0 | 0 | |
ACBUS0 | 0 | 0 | |
ACBUS1 | nSRST | 0 | 1 |
ACBUS2 | 0 | 0 | |
ACBUS3 | LED | 1 | 1 |
ACBUS4 | 0 | 0 | |
ACBUS5 | 0 | 0 | |
ACBUS6 | 0 | 0 | |
ACBUS7 | 0 | 0 |
Function內容是筆者自行推測的,Olimex並沒有釋出任何Schematic
再來就是呼叫mpsse_set_data_bits_low_byte()和mpsse_set_data_bits_high_byte()分別設定Low Byte的Value/Direction和High Byte的Value/Direction,
可以參考以下內容(src/jtag/drivers/mpsse.c):
void mpsse_set_data_bits_low_byte(struct mpsse_ctx *ctx, uint8_t data, uint8_t dir)
{
DEBUG_IO("-");
if (ctx->retval != ERROR_OK) {
DEBUG_IO("Ignoring command due to previous error");
return;
}
if (buffer_write_space(ctx) < 3)
ctx->retval = mpsse_flush(ctx);
buffer_write_byte(ctx, 0x80);
buffer_write_byte(ctx, data);
buffer_write_byte(ctx, dir);
}
void mpsse_set_data_bits_high_byte(struct mpsse_ctx *ctx, uint8_t data, uint8_t dir)
{
DEBUG_IO("-");
if (ctx->retval != ERROR_OK) {
DEBUG_IO("Ignoring command due to previous error");
return;
}
if (buffer_write_space(ctx) < 3)
ctx->retval = mpsse_flush(ctx);
buffer_write_byte(ctx, 0x82);
buffer_write_byte(ctx, data);
buffer_write_byte(ctx, dir);
}
以上述範例的Value=0x0808、Direction=0x0a1b來說,
就是送出以下兩筆Commands:
最後別忘記把loopback功能給關掉:
mpsse_loopback_config(mpsse_ctx, false);
void mpsse_loopback_config(struct mpsse_ctx *ctx, bool enable)
{
LOG_DEBUG("%s", enable ? "on" : "off");
single_byte_boolean_helper(ctx, enable, 0x84, 0x85);
}
就是簡單的送出"0x85",關閉Loopback!
最後,就是要來設定JTAG中TCK的頻率:
///[譯註] Step 3: Set Adapter Frequency
freq = mpsse_set_frequency(mpsse_ctx, jtag_get_speed_khz() * 1000);
一樣,來看一下mpsse_set_frequency()的內容,
請參考以下(src/jtag/drivers/mpsse.c):
int mpsse_set_frequency(struct mpsse_ctx *ctx, int frequency)
{
LOG_DEBUG("target %d Hz", frequency);
assert(frequency >= 0);
int base_clock;
if (frequency == 0)
return mpsse_rtck_config(ctx, true);
mpsse_rtck_config(ctx, false); /* just try */
if (frequency > 60000000 / 2 / 65536 && mpsse_divide_by_5_config(ctx, false) == ERROR_OK) {
base_clock = 60000000;
} else {
mpsse_divide_by_5_config(ctx, true); /* just try */
base_clock = 12000000;
}
int divisor = (base_clock / 2 + frequency - 1) / frequency - 1;
if (divisor > 65535)
divisor = 65535;
assert(divisor >= 0);
mpsse_set_divisor(ctx, divisor);
frequency = base_clock / 2 / (1 + divisor);
LOG_DEBUG("actually %d Hz", frequency);
return frequency;
}
這邊就比較簡單啦!!!
首先是判斷Frequency有沒有設定,在OpenOCD中,如果Frequency為0,表示開啟RTCK的功能:
if (frequency == 0)
return mpsse_rtck_config(ctx, true);
不過RTCK在這系列中並不會提到,先跳過!
如果是正常的使用JTAG中的TCK,那就需要把FT2232H的RTCK給關閉(src/jtag/drivers/mpsse.c):
mpsse_rtck_config(ctx, false); /* just try */
int mpsse_rtck_config(struct mpsse_ctx *ctx, bool enable)
{
if (!mpsse_is_high_speed(ctx))
return ERROR_FAIL;
LOG_DEBUG("%s", enable ? "on" : "off");
single_byte_boolean_helper(ctx, enable, 0x96, 0x97);
return ERROR_OK;
}
就是簡單的送出"0x97",來關閉RTCK功能!
接下來是要判斷Base Clock的部分!
FT2232H中支援60 MHz和12 MHz(Divide by 5)兩種Base Clock:
if (frequency > 60000000 / 2 / 65536 && mpsse_divide_by_5_config(ctx, false) == ERROR_OK) {
base_clock = 60000000;
} else {
mpsse_divide_by_5_config(ctx, true); /* just try */
base_clock = 12000000;
}
如果要使用12 MHz,就呼叫mpsse_divide_by_5_config()
,送出"0x8b",來開啟"Divide by 5"的功能(src/jtag/drivers/mpsse.c):
int mpsse_divide_by_5_config(struct mpsse_ctx *ctx, bool enable)
{
if (!mpsse_is_high_speed(ctx))
return ERROR_FAIL;
LOG_DEBUG("%s", enable ? "on" : "off");
single_byte_boolean_helper(ctx, enable, 0x8b, 0x8a);
return ERROR_OK;
}
接下來是計算除數:
int divisor = (base_clock / 2 + frequency - 1) / frequency - 1;
if (divisor > 65535)
divisor = 65535;
assert(divisor >= 0);
然後呼叫mpsse_set_divisor()
,把除數寫入(src/jtag/drivers/mpsse.c):
void mpsse_set_divisor(struct mpsse_ctx *ctx, uint16_t divisor)
{
LOG_DEBUG("%d", divisor);
if (ctx->retval != ERROR_OK) {
DEBUG_IO("Ignoring command due to previous error");
return;
}
if (buffer_write_space(ctx) < 3)
ctx->retval = mpsse_flush(ctx);
buffer_write_byte(ctx, 0x86);
buffer_write_byte(ctx, divisor & 0xff);
buffer_write_byte(ctx, divisor >> 8);
}
這邊就是把算好的Divisor拆成ValueL, ValueH,依照"0x86, 0xValueL, 0xValueH"的格式送出!
打到一半才發現,光講完FT2232H初始化的部分,就已經篇幅太長了....
所以只好將JTAG Command Execution放到明天~!
讓我們明天再會啦!!
static bool open_matching_device(struct mpsse_ctx *ctx, const uint16_t *vid, const uint16_t *pid,
const char *product, const char *serial, const char *location)
{
libusb_device **list;
struct libusb_device_descriptor desc;
struct libusb_config_descriptor *config0;
int err;
bool found = false;
ssize_t cnt = libusb_get_device_list(ctx->usb_ctx, &list);
if (cnt < 0)
LOG_ERROR("libusb_get_device_list() failed with %s", libusb_error_name(cnt));
for (ssize_t i = 0; i < cnt; i++) {
libusb_device *device = list[i];
err = libusb_get_device_descriptor(device, &desc);
if (err != LIBUSB_SUCCESS) {
LOG_ERROR("libusb_get_device_descriptor() failed with %s", libusb_error_name(err));
continue;
}
if (vid && *vid != desc.idVendor)
continue;
if (pid && *pid != desc.idProduct)
continue;
err = libusb_open(device, &ctx->usb_dev);
if (err != LIBUSB_SUCCESS) {
LOG_ERROR("libusb_open() failed with %s",
libusb_error_name(err));
continue;
}
if (location && !device_location_equal(device, location)) {
libusb_close(ctx->usb_dev);
continue;
}
if (product && !string_descriptor_equal(ctx->usb_dev, desc.iProduct, product)) {
libusb_close(ctx->usb_dev);
continue;
}
if (serial && !string_descriptor_equal(ctx->usb_dev, desc.iSerialNumber, serial)) {
libusb_close(ctx->usb_dev);
continue;
}
found = true;
break;
}
libusb_free_device_list(list, 1);
if (!found) {
LOG_ERROR("no device found");
return false;
}
err = libusb_get_config_descriptor(libusb_get_device(ctx->usb_dev), 0, &config0);
if (err != LIBUSB_SUCCESS) {
LOG_ERROR("libusb_get_config_descriptor() failed with %s", libusb_error_name(err));
libusb_close(ctx->usb_dev);
return false;
}
/* Make sure the first configuration is selected */
int cfg;
err = libusb_get_configuration(ctx->usb_dev, &cfg);
if (err != LIBUSB_SUCCESS) {
LOG_ERROR("libusb_get_configuration() failed with %s", libusb_error_name(err));
goto error;
}
if (desc.bNumConfigurations > 0 && cfg != config0->bConfigurationValue) {
err = libusb_set_configuration(ctx->usb_dev, config0->bConfigurationValue);
if (err != LIBUSB_SUCCESS) {
LOG_ERROR("libusb_set_configuration() failed with %s", libusb_error_name(err));
goto error;
}
}
/* Try to detach ftdi_sio kernel module */
err = libusb_detach_kernel_driver(ctx->usb_dev, ctx->interface);
if (err != LIBUSB_SUCCESS && err != LIBUSB_ERROR_NOT_FOUND
&& err != LIBUSB_ERROR_NOT_SUPPORTED) {
LOG_WARNING("libusb_detach_kernel_driver() failed with %s, trying to continue anyway",
libusb_error_name(err));
}
err = libusb_claim_interface(ctx->usb_dev, ctx->interface);
if (err != LIBUSB_SUCCESS) {
LOG_ERROR("libusb_claim_interface() failed with %s", libusb_error_name(err));
goto error;
}
/* Reset FTDI device */
err = libusb_control_transfer(ctx->usb_dev, FTDI_DEVICE_OUT_REQTYPE,
SIO_RESET_REQUEST, SIO_RESET_SIO,
ctx->index, NULL, 0, ctx->usb_write_timeout);
if (err < 0) {
LOG_ERROR("failed to reset FTDI device: %s", libusb_error_name(err));
goto error;
}
switch (desc.bcdDevice) {
case 0x500:
ctx->type = TYPE_FT2232C;
break;
case 0x700:
ctx->type = TYPE_FT2232H;
break;
case 0x800:
ctx->type = TYPE_FT4232H;
break;
case 0x900:
ctx->type = TYPE_FT232H;
break;
default:
LOG_ERROR("unsupported FTDI chip type: 0x%04x", desc.bcdDevice);
goto error;
}
/* Determine maximum packet size and endpoint addresses */
if (!(desc.bNumConfigurations > 0 && ctx->interface < config0->bNumInterfaces
&& config0->interface[ctx->interface].num_altsetting > 0))
goto desc_error;
const struct libusb_interface_descriptor *descriptor;
descriptor = &config0->interface[ctx->interface].altsetting[0];
if (descriptor->bNumEndpoints != 2)
goto desc_error;
ctx->in_ep = 0;
ctx->out_ep = 0;
for (int i = 0; i < descriptor->bNumEndpoints; i++) {
if (descriptor->endpoint[i].bEndpointAddress & 0x80) {
ctx->in_ep = descriptor->endpoint[i].bEndpointAddress;
ctx->max_packet_size =
descriptor->endpoint[i].wMaxPacketSize;
} else {
ctx->out_ep = descriptor->endpoint[i].bEndpointAddress;
}
}
if (ctx->in_ep == 0 || ctx->out_ep == 0)
goto desc_error;
libusb_free_config_descriptor(config0);
return true;
desc_error:
LOG_ERROR("unrecognized USB device descriptor");
error:
libusb_free_config_descriptor(config0);
libusb_close(ctx->usb_dev);
return false;
}